/*
 * Author: vdaras
 */


#include "../sdl/Color.h"
#include "../sdl/Surface.h"
#include "Text.h"
#include "Graphics.h"


namespace gui
{


Text::Text()
:
m_cursorRow(0),
m_cursorColumn(0)
{
}


/*
 * This constructor sets the contents of the text object. It breaks the argument
 * into rows based on newline characters.
 *
 * @param
 * - text: contents
 */

Text::Text(const std::string &text)
:
m_cursorRow(0),
m_cursorColumn(0)
{
    unsigned pos = 0, previousPos = 0;
    bool complete = false;

    do
    {
        //find position of the first newline for the rest of the text
        pos = text.find('\n', previousPos);

        //if no newline character exists
        if(pos == std::string::npos)
        {
            //flag that prcoessing the text is complete
            complete = true;
            //append rest of the text to the rows
            m_rows.push_back(text.substr(previousPos, text.length() - previousPos));
        }
        else
        {
            //append substring from previous position till the new line position to the rows
            m_rows.push_back(text.substr(previousPos, pos - previousPos));
        }

        //save previous position
        previousPos = pos + 1;

    }
    while(!complete);
}


Text::~Text()
{
}


/**
 * This routine inserts a character after the cursor.
 *
 * @param c: the character to insert.
 * @param times: how many times to insert
 */

void Text::InsertCharacter(char c, int times)
{
    //if it's a newline character
    if(c == '\n')
    {
        if(!m_rows.empty())
        {
            //must split the current row into two rows
            std::string &currentRow = m_rows[m_cursorRow];

            //get rest of the line after the cursor
            std::string restLine = currentRow.substr(m_cursorColumn, currentRow.length() - m_cursorColumn);
            //insert it after current cursor's row
            m_rows.insert(m_rows.begin() + m_cursorRow + 1, restLine);
            m_rows[m_cursorRow] = m_rows[m_cursorRow].substr(0, m_cursorColumn);
        }
        else
        {
            m_rows.push_back("");
        }
    }
    else //else
    {
        if(!m_rows.empty())
        {
            std::string &currentRow = m_rows[m_cursorRow];
            //insert character after the cursor
            currentRow.insert(currentRow.begin() + m_cursorColumn, times, c);
        }
        else
        {
            m_rows.push_back("");
            m_rows[m_cursorRow].append(times, c);
        }
    }
}


/**
 * This routine deletes characters after or before the cursor. If a negative
 * value is supplied as an offset then it removes 'abs(offset)' characters BEFORE the cursor.
 * If the supplied offset is positive, then it removes 'offset' characters AFTER the cursor.
 *
 * @param offset: offset from cursor of the first character to delete
 */

void Text::DeleteCharacters(int offset)
{
    std::string &currentRow = m_rows[m_cursorRow];
    //if offset is positive
    if(offset > 0)
    {
        //delete 'offset' characters after the cursor
        //first we are making sure that we don't get out of the bounds of the current row

        //if current cursor's column plus the offset is bigger than the current row
        if(m_cursorColumn + offset >= currentRow.length())
        {
            //set the offset as the row's length minus the current cursor's column
            offset = currentRow.length() - m_cursorColumn;
        }

        //delete characters
        currentRow.erase(currentRow.begin() + m_cursorColumn, currentRow.begin() + m_cursorColumn + offset);
    }
    else
    {
        //get absolute value of offset
        offset = abs(offset);
        int firstToDelete = static_cast<int> (m_cursorColumn) - offset;

        //if the first character to delete from the current row is below zero
        if(firstToDelete < 0)
        {
            //the offset gets beyond the first character of the row, remove extra characters
            offset = offset - static_cast<int> (m_cursorColumn) - 1;
        }

        //delete characters
        currentRow.erase(currentRow.begin() + m_cursorColumn - offset, currentRow.begin() + m_cursorColumn);

        //move cursor
        MoveCursorLeft();
    }


}


/**
 * Returns text from the row where the cursor is located currently.
 *
 * @return text
 */

std::string Text::GetCurrentRow() const
{
    if(m_rows.empty())
    {
        return "";
    }

    return m_rows[m_cursorRow];
}


/**
 * Set's cursor position in the text.
 *
 * @param row: the desired row for the cursor
 * @param column: the desired column for the cursor
 */

void Text::SetCursorPosition(unsigned row, unsigned column)
{
    //if the desired row is higher than the total rows or the desired column is higher than the size of the desired row
    if(row >= m_rows.size() || column > m_rows[row].length())
    {
        //do nothing
        return;
    }

    m_cursorRow = row;
    m_cursorColumn = column;
}


/**
 * This routine take a 2D point and a font in order to translate it to
 * a cursor position inside the text.
 *
 * @param pointX: point's X coordinate
 * @param pointY: point's Y coordinate
 * @param font: the font to use in order to calculate text size
 */

void Text::SetCursorFromPoint(int pointX, int pointY, sdl::Font *fnt)
{
    if(m_rows.empty())
        return;

    int substringWidth, substringHeight;
    unsigned newCursorRow = pointY / fnt->GetHeight();
    unsigned newCursorColumn = 0;

    //check new row, if it's out of bounds reutrn
    if(newCursorRow < 0 || newCursorRow >= m_rows.size())
        return;

    std::string &currentRow = m_rows[newCursorRow];

    //for each substring of the current row
    for(int lastIndex = currentRow.length() - 1; lastIndex >= 0; --lastIndex)
    {
        //get substring's width
        fnt->TextSize(currentRow.substr(0, lastIndex), substringWidth, substringHeight);

        //if point's x is greater than the substring's width
        if(pointX > substringWidth)
        {
            //then substring's last index is the new column
            newCursorColumn = (unsigned) lastIndex;
            SetCursorPosition(newCursorRow, newCursorColumn);
            return;
        }
    }
}


/**
 * Returns current row of the cursor.
 *
 * @return cursor's row.
 */

unsigned Text::GetCursorRow() const
{
    return m_cursorRow;
}


/**
 * Returns current column of the cursor.
 *
 * @return cursor's column.
 */

unsigned Text::GetCursorColumn() const
{
    return m_cursorColumn;
}


/**
 * This method moves the cursor to the right. The cursor's current column cannot be
 * higher than the length of the string.
 */

void Text::MoveCursorRight()
{
    //calculate new cursor's column
    unsigned newCursorColumn = m_cursorColumn + 1;

    //if it's not bigger than the length of the current row's string
    if(newCursorColumn <= m_rows[m_cursorRow].length())
    {
        //make movement
        ++m_cursorColumn;
    }
}


/**
 * This method moves the cursor to the left. The cursor's current column cannot be lower
 * than zero.
 */

void Text::MoveCursorLeft()
{
    //calculate new cursor's column
    int newCursorColumn = m_cursorColumn - 1;

    //if bigger than zero
    if(newCursorColumn >= 0)
    {
        //make movement
        --m_cursorColumn;
    }
}


/**
 * This method moves the cursor upwards. The cursor's current row cannot be lower
 * than zero.
 */

void Text::MoveCursorUp()
{
    //calculate new cursor's row
    int newCursorRow = static_cast<int> (m_cursorRow) - 1;

    //if bigger than zero
    if(newCursorRow >= 0)
    {
        //make movement
        --m_cursorRow;

        //if current cursor's column is bigger than the current row's size
        if(m_cursorColumn > m_rows[m_cursorRow].size())
        {
            //set the cursor's column as the row's size
            m_cursorColumn = m_rows[m_cursorRow].size();
        }
    }
}


/**
 * This method moves the cursor downwards. The cursor's current row cannot be greater
 * than the total rows of the text.
 */

void Text::MoveCursorDown()
{
    //calculate new cursor's row
    unsigned newCursorRow = m_cursorRow + 1;

    //if new cursors row is lower than the total rows
    if(newCursorRow < m_rows.size())
    {
        //make movement
        ++m_cursorRow;

        //if current cursor's column is bigger than the current row's size
        if(m_cursorColumn > m_rows[m_cursorRow].size())
        {
            //set the cursor's column as the row's size
            m_cursorColumn = m_rows[m_cursorRow].size();
        }
    }
}


/**
 * Returns x coordinate of the cursor, starting from 0.
 *
 * @param font: font used to calculate text dimension.
 * @return cursor x
 */

int Text::GetCursorX(sdl::Font* font) const
{
    //if there is no text yet
    if(m_rows.empty())
    {
        //the cursor is located at x = 0 position
        return 0;
    }

    //the current x of the cursor is the width of the substring before it,
    //plus one to separate from the previous letter.
    const std::string &currentRow = m_rows[m_cursorRow];

    int width, height;
    font->TextSize(currentRow.substr(0, m_cursorColumn), width, height);
    return width + 1;
}


/**
 * Returns y coordinate of the cursor, starting from 0.
 *
 * @param font: font used to calculate text height
 * @return cursor's y
 */

int Text::GetCursorY(sdl::Font *font) const
{
    //if there is no text yet
    if(m_rows.empty())
    {
        //the cursor is located at y = 0 position
        return 0;
    }

    //cursor's is determined by multiplying the cursor's row by the font's height
    return m_cursorRow * font->GetHeight();
}


/**
 * This routine returns the contents of the Text.
 *
 * @param context of text in a string.
 */

std::string Text::GetContents() const
{
    //create a temporary string to return the contents in it
    std::string toReturn;

    //for each row
    std::vector< std::string >::const_iterator iter(m_rows.begin()), end(m_rows.end());

    for(; iter != end; ++iter)
    {
        //append row to the result
        toReturn += (*iter);
        //append a newline character
        toReturn.push_back('\n');
    }

    //return result
    return toReturn;
}

};
